package com.programmingteam.wallpaperslideshow.imagesearch;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.ProviderException;
import java.util.LinkedList;
import java.util.ListIterator;

import org.apache.http.client.ClientProtocolException;
import org.json.JSONException;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.location.Location;
import android.preference.PreferenceManager;

import com.programmingteam.wallpaperslideshow.Config;
import com.programmingteam.wallpaperslideshow.contentprovider.ImageProvider;

public class SearchController {

	public static final String TAG = "WallpaperSlideshow";

	private Location mLocation;
	private SharedPreferences mPrefs;
	private ContentResolver mContentResolver;

	/**
	 * @param l location to be searched. If null passed, controller will use last saved location
	 * @param c application context
	 */
	public SearchController(Location l, Context c) {
		mPrefs = PreferenceManager.getDefaultSharedPreferences(c);
		mContentResolver = c.getContentResolver();
		mLocation = (l == null) ? getLastSearchLocation() : l;
	}

	/**
	 * Search using last saved location
	 * @param c application context
	 */
	public SearchController(Context c) {
		this(null, c);
	}

	public PhotoData getNextPhoto() throws ProviderException, ClientProtocolException, URISyntaxException, IOException, JSONException {
		boolean parametersChanged;
		boolean resultsAvailable;

		parametersChanged = checkParametersChange();
		resultsAvailable = checkResultsAvailable();

		if (parametersChanged || !resultsAvailable) {
			doSearchImages(parametersChanged);
		}

		return getNextResult();
	}

	private boolean checkParametersChange() {
		boolean flgParameterChanged = false;
		float pLat, pLon;
		int accuracy, pAccuracy, imageQuality, pImageQuality;
		Location location, pLocation;
		String keywords, pKeywords, searchProviderName, pSearchProviderName;

		// Current search parameters
		location = mLocation;
		keywords = mPrefs.getString(Config.SharedKeys.SEARCH_KEYWORDS, Config.SharedDefaults.SEARCH_KEYWORDS);
		accuracy = mPrefs.getInt(Config.SharedKeys.EVENT_LOCATION_DISTANCE, Config.SharedDefaults.EVENT_LOCATION_DISTANCE);
		imageQuality = mPrefs.getInt(Config.SharedKeys.IMAGEQUALITY, Config.SharedDefaults.IMAGEQUALITY);
		searchProviderName = mPrefs.getString(Config.SharedKeys.SERVICE_NAME, Config.SharedDefaults.SERVICE_NAME);

		// Previous search parameters
		pLat = mPrefs.getFloat(Config.SharedKeys.PSEARCH_LATITUDE, Config.SharedDefaults.PSEARCH_LATITUDE);
		pLon = mPrefs.getFloat(Config.SharedKeys.PSEARCH_LONGITUDE, Config.SharedDefaults.PSEARCH_LONGITUDE);
		pLocation = new Location("FAKE");
		pLocation.setLatitude(pLat);
		pLocation.setLongitude(pLon);
		pKeywords = mPrefs.getString(Config.SharedKeys.PSEARCH_KEYWORDS, Config.SharedDefaults.PSEARCH_KEYWORDS);
		pAccuracy = mPrefs.getInt(Config.SharedKeys.PSEARCH_ACCURACY, Config.SharedDefaults.PSEARCH_ACCURACY);
		pImageQuality = mPrefs.getInt(Config.SharedKeys.PSEARCH_QUALITY, Config.SharedDefaults.PSEARCH_QUALITY);
		pSearchProviderName = mPrefs.getString(Config.SharedKeys.PSEARCH_SERVICENAME, Config.SharedDefaults.PSEARCH_SERVICENAME);

		// Test for changes
		flgParameterChanged = !keywords.equals(pKeywords)
				|| accuracy != pAccuracy
				|| imageQuality != pImageQuality
				|| !searchProviderName.equals(pSearchProviderName)
				|| (location != null && (location.distanceTo(pLocation) > accuracy * 1000));

		return flgParameterChanged;
	}

	private boolean checkResultsAvailable() {
		Cursor c;
		boolean resultsAvailable;

		c = mContentResolver.query(ImageProvider.SearchResultsTable.CONTENT_URI, null, null, null, null);
		resultsAvailable = c.moveToFirst();
		c.close();

		return resultsAvailable;
	}

	// TODO Better logging!
	private void doSearchImages(boolean parametersChanged) throws ProviderException, ClientProtocolException, URISyntaxException, IOException, JSONException {
		ImageSearch imageSearch;
		Location l;
		String keywords, searchProviderName;
		int accuracy, imageQuality, page, extendedSearchLvl, searchLoopNbr = 0;
		PhotoData[] searchResults = null;

		l = mLocation;
		keywords = mPrefs.getString(Config.SharedKeys.SEARCH_KEYWORDS, Config.SharedDefaults.SEARCH_KEYWORDS);
		accuracy = mPrefs.getInt(Config.SharedKeys.EVENT_LOCATION_DISTANCE, Config.SharedDefaults.EVENT_LOCATION_DISTANCE);
		imageQuality = mPrefs.getInt(Config.SharedKeys.IMAGEQUALITY, Config.SharedDefaults.IMAGEQUALITY);
		searchProviderName = mPrefs.getString(Config.SharedKeys.SERVICE_NAME, Config.SharedDefaults.SERVICE_NAME);
		page = parametersChanged ? 1 :
				mPrefs.getInt(Config.SharedKeys.PSEARCH_PAGE, Config.SharedDefaults.PSEARCH_PAGE) + 1;
		extendedSearchLvl = parametersChanged ? 0 :
				mPrefs.getInt(Config.SharedKeys.PSEARCH_EXTENDED, Config.SharedDefaults.PSEARCH_EXTENDED);
		imageSearch = getSearchService(searchProviderName);

		while (searchResults == null && searchLoopNbr < Config.ImageSearch.MAX_SEARCH_LOOPS) {
			// Step 1
			searchResults = imageSearch.searchImages(l, keywords, accuracy * (1 + 5 * extendedSearchLvl), imageQuality, page);
			if (searchResults == null) {
				++searchLoopNbr;
				++extendedSearchLvl;
				page = 1;
				continue;
			}
			// Step 2
			searchResults = removeDuplicates(searchResults);
			if (searchResults == null) {
				++page;
				++searchLoopNbr;
			}
		}

		if (searchResults != null) {
			saveSearchResults(searchResults);
			saveSearchState(l.getLatitude(), l.getLongitude(), keywords,
					accuracy, page, imageQuality, searchProviderName, extendedSearchLvl);
		}
		else {
			throw new ProviderException("Empty search results");
		}
	}

	private ImageSearch getSearchService(String svcName) {
		ImageSearch searchSvc;
		if (svcName.equals(Config.Const.PROVIDER_NAME_PANORAMIO)) {
			searchSvc = new ImageSearchPanoramio();
		}
		else {
			searchSvc = new ImageSearchPanoramio();
		}

		return searchSvc;
	}

	private PhotoData[] removeDuplicates(PhotoData[] pd) {
		if (pd == null)
			return null;

		Cursor c;
		PhotoData[] uniquePhotos = null;
		LinkedList<Integer> uniqueIds;
		ListIterator<Integer> ite;

		uniqueIds = new LinkedList<Integer>();
		for (int i = 0; i < pd.length; i++) {
			// TODO Change query where to in() clause
			c = mContentResolver.query(ImageProvider.ImagesTable.CONTENT_URI, null,
					ImageProvider.ImagesTable.PHOTO_ID + "=" + pd[i].getImageId() + " and "
							+ ImageProvider.ImagesTable.PHOTO_PROVIDER + "='" + pd[i].getProvider() + "'",
					null, null);

			if (!c.moveToFirst()) {
				uniqueIds.add(i);
			}
		}

		if (uniqueIds.size() > 0) {
			uniquePhotos = new PhotoData[uniqueIds.size()];
			ite = uniqueIds.listIterator();
			for (int i = 0; ite.hasNext(); i++) {
				uniquePhotos[i] = pd[ite.next()];
			}
		}

		return uniquePhotos;
	}

	private void saveSearchState(double lat, double lon, String keywords, int accuracy, int page, int quality, String svcName, int extendedSearch) {
		Editor editor;
		editor = mPrefs.edit();
		editor.putFloat(Config.SharedKeys.PSEARCH_LATITUDE, (float) lat);
		editor.putFloat(Config.SharedKeys.PSEARCH_LONGITUDE, (float) lon);
		editor.putString(Config.SharedKeys.PSEARCH_KEYWORDS, keywords);
		editor.putInt(Config.SharedKeys.PSEARCH_ACCURACY, accuracy);
		editor.putInt(Config.SharedKeys.PSEARCH_PAGE, page);
		editor.putInt(Config.SharedKeys.PSEARCH_QUALITY, quality);
		editor.putString(Config.SharedKeys.PSEARCH_SERVICENAME, svcName);
		editor.putInt(Config.SharedKeys.PSEARCH_EXTENDED, extendedSearch);
		editor.commit();
	}

	private void saveSearchResults(PhotoData[] pd) {
		if (pd == null)
			return;
		ContentValues vals;
		PhotoData photoData;

		//Delete last search results
		mContentResolver.delete(ImageProvider.SearchResultsTable.CONTENT_URI, "1=1", null);

		for (int i = 0; i < pd.length; i++) {
			photoData = pd[i];
			vals = new ContentValues();

			vals.put(ImageProvider.SearchResultsTable.PHOTO_ID, photoData.getImageId());
			vals.put(ImageProvider.SearchResultsTable.PHOTO_PROVIDER, photoData.getProvider());
			vals.put(ImageProvider.SearchResultsTable.PHOTO_PAGE_URI, "" + photoData.getSourcePageUri());
			vals.put(ImageProvider.SearchResultsTable.PHOTO_URI, "" + photoData.getImageUri());
			vals.put(ImageProvider.SearchResultsTable.PHOTO_DESC, photoData.getPhtotoDescription());
			vals.put(ImageProvider.SearchResultsTable.PHOTO_PLACE, photoData.getPhotoNearPlace());

			mContentResolver.insert(ImageProvider.SearchResultsTable.CONTENT_URI, vals);
		}
	}

	private PhotoData getNextResult() {
		Cursor c;
		PhotoData photoData = null;
		ImageSearch imageSearch;
		String searchProviderName;

		// Get newest search result
		c = mContentResolver.query(ImageProvider.SearchResultsTable.CONTENT_URI, null, null, null, "_ID asc");
		if (c.moveToFirst()) {
			photoData = new PhotoData();
			photoData.setImageId(c.getLong(c.getColumnIndex(ImageProvider.SearchResultsTable.PHOTO_ID)));
			photoData.setProvider(c.getString(c.getColumnIndex(ImageProvider.SearchResultsTable.PHOTO_PROVIDER)));
			photoData.setSourcePageUri(URI.create(c.getString(c.getColumnIndex(ImageProvider.SearchResultsTable.PHOTO_PAGE_URI))));
			photoData.setImageUri(URI.create(c.getString(c.getColumnIndex(ImageProvider.SearchResultsTable.PHOTO_URI))));
			photoData.setPhtotoDescription(c.getString(c.getColumnIndex(ImageProvider.SearchResultsTable.PHOTO_DESC)));
			photoData.setPhotoNearPlace(c.getString(c.getColumnIndex(ImageProvider.SearchResultsTable.PHOTO_PLACE)));

			mContentResolver.delete(ImageProvider.SearchResultsTable.CONTENT_URI,
					ImageProvider.SearchResultsTable.PHOTO_ID + "=" + photoData.getImageId(), null);
		}

		// Download detailed photo data
		searchProviderName = mPrefs.getString(Config.SharedKeys.SERVICE_NAME, Config.SharedDefaults.SERVICE_NAME);
		imageSearch = getSearchService(searchProviderName);
		photoData = imageSearch.getImageDetails(photoData);

		return photoData;
	}

	private Location getLastSearchLocation() {
		float pLat, pLon;
		Location location;

		// Retreive last saved location
		pLat = mPrefs.getFloat(Config.SharedKeys.PSEARCH_LATITUDE, Config.SharedDefaults.PSEARCH_LATITUDE);
		pLon = mPrefs.getFloat(Config.SharedKeys.PSEARCH_LONGITUDE, Config.SharedDefaults.PSEARCH_LONGITUDE);
		location = new Location("FAKE");
		location.setLatitude(pLat);
		location.setLongitude(pLon);

		return location;
	}

}
